#ifndef __OWLQN_H__
#define __OWLQN_H__

#include <vector>
#include <deque>
#include <iostream>

#include "type_def.h"
#include "optim_problem.h"

class OwlqnOptimizer {
public:
	OptimProblem *problem;
	TerminationCriterion *termCrit;

	OwlqnOptimizer() {
		termCrit = new RelativeMeanImprovementCriterion(5);
	}

	~OwlqnOptimizer() {
		if (termCrit)
			delete termCrit;
	}

	void minimize(OptimProblem& problem, const DVector& initial, DVector& minimum, double l1weight = 1.0, double tol =
			1e-4) const;
};

class OptimizerState {
	friend class OwlqnOptimizer;

	struct DblVecPtrDeque: public std::deque<DVector*> {
		~DblVecPtrDeque() {
			for (size_t s = 0; s < size(); ++s) {
				if ((*this)[s] != NULL)
					delete (*this)[s];
			}
		}
	};

	DVector x, grad, newX, newGrad, dir;
	DVector& steepestDescDir; // references newGrad to save memory, since we don't ever use both at the same time
	DblVecPtrDeque sList, yList;
	std::deque<double> roList;
	std::vector<double> alphas;
	double value;
	int iter, m;
	const size_t dim;
	OptimProblem& problem;
	double l1weight;

	void MapDirByInverseHessian();
	void UpdateDir();
	double DirDeriv() const;
	void GetNextPoint(double alpha);
	void BackTrackingLineSearch();
	void Shift();
	void MakeSteepestDescDir();
	double EvalL1();
	void FixDirSigns();
	void TestDirDeriv();

	OptimizerState(OptimProblem& p, const DVector& init, int m, double l1weight, bool quiet) :
			x(init), grad(init.size()), newX(init), newGrad(init.size()), dir(init.size()), steepestDescDir(newGrad), alphas(
					m), iter(1), m(m), dim(init.size()), problem(p), l1weight(l1weight) {
		if (m <= 0) {
			std::cerr << "m must be an integer greater than zero." << std::endl;
			exit(1);
		}
		value = EvalL1();
		grad = newGrad;
	}

public:
	const DVector& GetX() const {
		return newX;
	}
	const DVector& GetLastX() const {
		return x;
	}
	const DVector& GetGrad() const {
		return newGrad;
	}
	const DVector& GetLastGrad() const {
		return grad;
	}
	const DVector& GetLastDir() const {
		return dir;
	}
	double GetValue() const {
		return value;
	}
	int GetIter() const {
		return iter;
	}
	size_t GetDim() const {
		return dim;
	}
};

#endif // __OWLQN_H__
